using System;
using System.Collections.Generic;
using System.Reflection;
using NFreeMarker.Template;

namespace NFreeMarker.Ext.Beans
{
    /**
     * This class is used for constructors and as a base for non-overloaded methods
     * @author Attila Szegedi
     * @version $Id: $
     */
    public class SimpleMemberModel // TODO: Was internal
    {
        private readonly MemberInfo _member;
        private readonly Type[] _argTypes;
        
        protected internal SimpleMemberModel(MemberInfo member, Type[] argTypes)
        {
            _member = member;
            _argTypes = argTypes;
        }

        internal object[] UnwrapArguments(IList<ITemplateModel> arguments, BeansWrapper wrapper)
        {
            if (arguments == null)
            {
                arguments = new List<ITemplateModel>().AsReadOnly();
            }
            bool varArg = MethodUtilities.IsVarArgs(_member);
            int typeLen = _argTypes.Length;
            if (varArg)
            {
                if (typeLen - 1 > arguments.Count)
                {
                    throw new TemplateModelException("Method " + _member +
                            " takes at least " + (typeLen - 1) +
                            " arguments");
                }
            }
            else if (typeLen != arguments.Count)
            {
                throw new TemplateModelException("Method " + _member +
                        " takes exactly " + typeLen + " arguments");
            }

            object[] args = UnwrapArguments(arguments, _argTypes, wrapper);
            if (args != null)
            {
                BeansWrapper.CoerceBigDecimals(_argTypes, args);
                if (varArg && ShouldPackVarArgs(args))
                {
                    args = PackVarArgs(args, _argTypes);
                }
            }
            return args;
        }

        static object[] UnwrapArguments(IList<ITemplateModel> arguments, Type[] argTypes, BeansWrapper w) 
        {
            if (arguments == null)
            {
                return null;
            }
            int argsLen = arguments.Count;
            int typeLen = argTypes.Length;
            var args = new object[argsLen];
            int min = Math.Min(argsLen, typeLen);
            IEnumerator<ITemplateModel> it = arguments.GetEnumerator();
            for (int i = 0; i < min; i++)
            {
                it.MoveNext();
                args[i] = UnwrapArgument(it.Current, argTypes[i], w);
            }
            for (int i = min; i < argsLen; i++)
            {
                it.MoveNext();
                args[i] = UnwrapArgument(it.Current, argTypes[min - 1], w);
            }
            return args;
        }

        private static object UnwrapArgument(ITemplateModel model, Type type, BeansWrapper w) 
        {
            object val = w.Unwrap(model, type);
            if (val == BeansWrapper.CanNotUnwrap)
            {
                throw new TemplateModelException("Can not unwrap argument " +
                        model + " to " + type.FullName);
            }
            return val;
        }
        
        private bool ShouldPackVarArgs(object[] args) {
            //int l = args.Length;
            //if(l == _argTypes.Length) {
            //    //assert l > 0; // varArg methods must have at least one declared arg
            //    object lastArg = args[l - 1];
            //    if(lastArg == null || _argTypes[l - 1].getComponentType().isInstance(lastArg)) {
            //        return false;
            //    }
            //}
            //return true;

            throw new NotImplementedException();
        }
        
        static object[] PackVarArgs(object[] args, Type[] argTypes)
        {
            int argsLen = args.Length;
            int typeLen = argTypes.Length;
            int fixArgsLen = typeLen - 1;
            Array varArray = Array.CreateInstance(
                argTypes[fixArgsLen],
                argsLen - fixArgsLen);
            for (int i = fixArgsLen; i < argsLen; i++)
            {
                varArray.SetValue(args[i], i - fixArgsLen);
            }
            if (argsLen != typeLen)
            {
                var newArgs = new object[typeLen];
                Array.Copy(args, 0, newArgs, 0, fixArgsLen);
                args = newArgs;
            }
            args[fixArgsLen] = varArray;
            return args;
        }

        protected internal MemberInfo GetMember()
        {
            return _member;
        }
    }
}